package yartiss.engine.simulation.io;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Document;
import org.w3c.dom.Element;

import yartiss.engine.simulation.system.ITask;
import yartiss.engine.simulation.system.SystemMetrics;
import yartiss.engine.simulation.system.schedulable.GraphTask;
import yartiss.engine.simulation.system.schedulable.SimpleTask;
import yartiss.engine.simulation.system.schedulable.SubTask;

/**
 * 
 * @author Manar Qamhieh & Frederic Fauberteau
 * 
 */
public class XmlFileWriter implements ITaskElementVisitor {

	private Document document;
	private Element mainElmt, tasksetElmt, taskElmt;
	private final OutputStream outputStream;

	public XmlFileWriter(String fileName, int nbTaskset, int nbTasks)
			throws Exception {
		this(new FileOutputStream(fileName), nbTaskset, nbTasks);
	}

	public XmlFileWriter(File outFile, int nbTaskset, int nbTasks)
			throws Exception {
		this(new FileOutputStream(outFile), nbTaskset, nbTasks);
	}

	public XmlFileWriter(OutputStream outputStream, int nbTaskset, int nbTasks)
			throws Exception {
		this.outputStream = outputStream;
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		DocumentBuilder builder = null;
		builder = factory.newDocumentBuilder();
		document = builder.newDocument();
		mainElmt = document.createElement("dataset");
		mainElmt.setAttribute("nbTasksets", Integer.toString(nbTaskset));
		mainElmt.setAttribute("nbTasks", Integer.toString(nbTasks));
		document.appendChild(mainElmt);
	}

	@Override
	public void visitTaskset(ITask[] taskSet, int pr) throws IOException {

		tasksetElmt = document.createElement("taskset");
		tasksetElmt.setAttribute("nbTasks", Integer.toString(taskSet.length));
		tasksetElmt.setAttribute("pr", Integer.toString(pr));
		tasksetElmt.setAttribute("utilization",
				Double.toString(SystemMetrics.utilisation(taskSet)));
		tasksetElmt.setAttribute("energyUtil",
				Double.toString(SystemMetrics.energyUtilization(taskSet, pr)));
		for (ITask task : taskSet) {
			if (task instanceof ITaskElement) {
				((ITaskElement) task).accept(this);
			}
		}
		mainElmt.appendChild(tasksetElmt);
		// writer.flush();
	}

	@Override
	public void visitGraphTask(GraphTask task) {
		taskElmt = document.createElement("task");
		taskElmt.setAttribute("type", task.getType());
		taskElmt.setAttribute("firstRelease",
				Long.toString(task.getFirstRelease()));

		taskElmt.setAttribute("wcet", Long.toString(task.getWcet()));
		taskElmt.setAttribute("wcee", Long.toString(task.getWcee()));
		taskElmt.setAttribute("period", Long.toString(task.getPeriod()));
		taskElmt.setAttribute("deadline", Long.toString(task.getDeadline()));
		taskElmt.setAttribute("priority", Integer.toString(task.getPriority()));
		taskElmt.setAttribute("nbSubtasks",
				Integer.toString(task.getSubtasks().size()));
		for (SubTask subtask : task.getSubtasks()) {
			subtask.accept(this);
		}
		tasksetElmt.appendChild(taskElmt);
	}

	@Override
	public void visitSimpleTask(SimpleTask task) throws IOException {
		Element taskElmt = document.createElement("task");

		taskElmt.setAttribute("type", task.getType());
		taskElmt.setAttribute("firstRelease",
				Long.toString(task.getFirstRelease()));
		taskElmt.setAttribute("wcet", Long.toString(task.getWcet()));
		taskElmt.setAttribute("wcee", Long.toString(task.getWcee()));
		taskElmt.setAttribute("period", Long.toString(task.getPeriod()));
		taskElmt.setAttribute("deadline", Long.toString(task.getDeadline()));
		taskElmt.setAttribute("priority", Integer.toString(task.getPriority()));

		tasksetElmt.appendChild(taskElmt);
	}

	@Override
	public void visitSubTask(SubTask task) {
		Element subtaskElmt = document.createElement("subtask");
		subtaskElmt.setAttribute("index", Integer.toString(task.getId()));
		subtaskElmt.setAttribute("wcet", Long.toString(task.getWcet()));
		subtaskElmt.setAttribute("firstRelease",
				Long.toString(task.getFirstRelease()));
		subtaskElmt.setAttribute("period", Long.toString(task.getPeriod()));
		subtaskElmt.setAttribute("deadline", Long.toString(task.getDeadline()));
		subtaskElmt.setAttribute("priority", Long.toString(task.getPriority()));
		subtaskElmt.setAttribute("type", task.getType());

		subtaskElmt.setAttribute("nbProc",
				Integer.toString(task.getNumOfProc()));

		String children = "", parents = "";
		if (task.getChildrenTasks().isEmpty())
			children = "-1";
		else
			for (SubTask child : task.getChildrenTasks())
				children += child.getId() + " ";
		subtaskElmt.setAttribute("children", children.trim());

		for (SubTask parent : task.getParentTasks()) {
			// Element parentElmt = document.createElement("parent");
			// parentElmt.setAttribute("index",
			// Integer.toString(parent.getId()));
			// subtaskElmt.appendChild(parentElmt);
			parents += parent.getId() + " ";
		}
		subtaskElmt.setAttribute("parents", parents);
		taskElmt.appendChild(subtaskElmt);
	}

	/**
	 * Closes the stream opened by this TasksetWriter. If this method is not
	 * called, then the XML stream is never flushed (Nothing is written outside
	 * the memory).
	 */
	@Override
	public void close() {
		TransformerFactory factory = TransformerFactory.newInstance();
		factory.setAttribute("indent-number", new Integer(2));
		Transformer transformer = null;
		try {
			transformer = factory.newTransformer();
		} catch (TransformerConfigurationException e) {
			e.printStackTrace();
		}
		transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
		transformer.setOutputProperty(OutputKeys.INDENT, "yes");
		DOMSource src = new DOMSource(document);
		try {
			StreamResult stream = new StreamResult(new OutputStreamWriter(
					outputStream, "UTF-8"));

			transformer.transform(src, stream);
		} catch (Exception e) {
			e.printStackTrace();
		}

	}

}
